﻿package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Timer;
	//import no.doomsday.console.ConsoleUtil;
	public class Maze extends Sprite {
		private static const CELL_BORDER:uint = 0xFFFFFF;
		//private static const CELL_IN:uint = 0xDFDFDF;
		private static const CELL_IN:uint = 0x7D450D;
		//private static const CELL_FRONTIER:uint = 0x268D03;
		private static const CELL_FRONTIER:uint = 0xFF9900;
		private static const CELL_OUT:uint = 0x000000;
		
		private static const BORDER_TOP:int = 1;
		private static const BORDER_RIGHT:int = 2;
		private static const BORDER_BOTTOM:int = 4;
		private static const BORDER_LEFT:int = 8;
		private static const BORDER_ALL:int = 15;
		
		private var scale:Number = 4;
		private var w:Number = int(800/scale/2)*2-1;
		private var h:Number = int(600/scale/2)*2-1;
		private var data:BitmapData;
		private var display:Bitmap;
		private var container:Sprite;
		private var displayMask:Shape;
		private var displayPos:Point = new Point();
		
		private var offset:Point = new Point();
		private var current:Point;
		private var frontiers:Vector.<Point> = new Vector.<Point>();
		private var target:Point = new Point();
		
		//private var topPadding:Number = 200;
		private var topPadding:Number = 50;
		
		private var runner:Timer = new Timer(10);
		//private var speed:int = 50;
		private var speed:int = 5;
		
		//private var edgeFlatness:Number = 0.5;
		//private var edgeFlatness:Number = 1;
		private var edgeFlatness:Number = 0;
		private var edgeFlatnessNoise:Number = 0;
		
		public function Maze():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			addChild(container = new Sprite());
			//container.x = container.y = 5;
			container.scaleX = container.scaleY = scale;
			
			h += topPadding;
			
			data = new BitmapData(w, h, false);
			display = new Bitmap(data);
			display.y = -topPadding;
			
			//display.mask = displayMask;
			
			container.addChild(display);
			
			displayMask = new Shape();
			var g:Graphics = displayMask.graphics;
			g.beginFill(0xFF0000);
			g.drawRect(1, 1, w-2, h-2-topPadding);
			
			display.mask = displayMask;
			
			container.addChild(displayMask);
			
			container.x = stage.stageWidth/2-container.width/2;
			
			fillCells(CELL_BORDER, CELL_OUT);
			//current = new Point(int(2+Math.floor(Math.random()*(w-4)/2)*2), int(2+Math.floor(Math.random()*(h-4)/2)*2));
			current = new Point(int(w/4)*2, 2);
			
			runner.addEventListener(TimerEvent.TIMER, run);
			runner.start();
			
			//addChild(ConsoleUtil.getInstance());
			
		}
		private function fillCells(border:uint, cells:uint, rect:Rectangle = null, borders:int = BORDER_ALL):void {
			if (!rect) {
				rect = data.rect.clone();
				rect.inflate(-1, -1);
			}
			data.fillRect(rect, cells);
			if (borders & BORDER_TOP)		data.fillRect(new Rectangle(rect.x, rect.top-1, rect.width, 1), border);
			if (borders & BORDER_BOTTOM)	data.fillRect(new Rectangle(rect.x, rect.bottom, rect.width, 1), border);
			if (borders & BORDER_LEFT)		data.fillRect(new Rectangle(rect.left-1, rect.y, 1, rect.height), border);
			if (borders & BORDER_RIGHT)		data.fillRect(new Rectangle(rect.right, rect.y, 1, rect.height), border);
		}
		public function setCell(x:int, y:int, v:uint, fade:Boolean = false):void {
			data.setPixel(x, y+offset.y, v);
		}
		public function getCell(x:int, y:int):uint {
			return data.getPixel(x, y+offset.y);
		}
		private function nextFrontier():Point {
			var minIndex:int = Math.random()*frontiers.length;
			var min:Point = frontiers[minIndex];
			var n:int = Math.min(Math.round(edgeFlatness+edgeFlatnessNoise*(Math.random()-0.5)), frontiers.length);
			for (var i:int = 0; i < n; i++) {
				var index:int = Math.random()*frontiers.length;
				var f:Point = frontiers[index];
				if (f.y > min.y) {
				//if (Point.distance(f, target) < Point.distance(min, target)) {
				//if (f.y > min.y && Point.distance(f, target) < Point.distance(min, target)) {
					min = f;
					minIndex = index;
				}
			}
			frontiers.splice(minIndex, 1);
			return min;
		}
		private function run(e:Event):void {
			//edgeFlatness = stage.mouseX*0.01;
			//edgeFlatness = stage.mouseY*0.001;
			//edgeFlatnessNoise = stage.mouseY*0.001;
			
			//edgeFlatness = stage.mouseY*0.05;
			//target.x = display.mouseX;
			//target.y = display.mouseY;
			
			for (var s:int = 0; s < speed; s++) {
				if (current.y+offset.y > 0) {
					setCell(current.x, current.y, CELL_IN);
					if (getCell(current.x, current.y-2) == CELL_OUT)	setFrontier(current.x, current.y-2);
					if (getCell(current.x-2, current.y) == CELL_OUT)	setFrontier(current.x-2, current.y);
					if (getCell(current.x+2, current.y) == CELL_OUT)	setFrontier(current.x+2, current.y);
					if (getCell(current.x, current.y+2) == CELL_OUT)	setFrontier(current.x, current.y+2);
				}
				if (frontiers.length > 0) {
					//current = frontiers.splice(int(Math.random()*frontiers.length), 1)[0];
					current = nextFrontier();
					var jumps:Vector.<Point> = new Vector.<Point>();
					if (getCell(current.x-2, current.y) == CELL_IN) jumps.push(new Point(current.x-2, current.y));
					if (getCell(current.x+2, current.y) == CELL_IN) jumps.push(new Point(current.x+2, current.y));
					if (getCell(current.x, current.y-2) == CELL_IN) jumps.push(new Point(current.x, current.y-2));
					if (getCell(current.x, current.y+2) == CELL_IN) jumps.push(new Point(current.x, current.y+2));
					if (jumps.length > 0) {
						var jump:Point = jumps[int(Math.random()*jumps.length)];
						setCell(int((current.x+jump.x)/2), int((current.y+jump.y)/2), CELL_IN);
					}
				}
				//if (current.y+offset.y > 0.85*h) {
				if (current.y+offset.y > 0.95*h) {
					var scrollAmount:int = 50;
					data.scroll(0, -scrollAmount);
					offset.y -= scrollAmount;
					moveDisplay(0, scrollAmount);
					fillCells(CELL_BORDER, CELL_OUT, new Rectangle(1, h-scrollAmount-1, w-2, scrollAmount), BORDER_LEFT | BORDER_RIGHT | BORDER_BOTTOM);
					fillCells(CELL_BORDER, CELL_OUT, new Rectangle(1, 1, w-2, 1), BORDER_LEFT | BORDER_RIGHT | BORDER_TOP);
				}
			}
		}
		private function moveDisplay(dx:int, dy:int):void {
			//displayPos.y += dy*10;
			//display.y = 5+displayPos.y;
			display.y += dy;
			TweenNano.killTweensOf(display);
			TweenNano.to(display, 0.5, { "y": -topPadding } );
		}
		private function fillBottom(border:int, color:int):void {
			data.setPixel(0, h-1, border);
			data.setPixel(w-1, h-1, border);
			data.fillRect(new Rectangle(1, h-1, w-2, 1), color);
		}
		private function setFrontier(x:int, y:int):void {
			frontiers.push(new Point(x, y));
			setCell(x, y, CELL_FRONTIER);
		}
	}
}